$KCODE='SJIS'

require 'keyword'

#notice in [A-Z] format, - has special meaning.
#so add \\ for escape -.
INDENT_PREX = "\*\\-\^o"
DEBUG = false


def debug(msg)
  return unless DEBUG

  unless $log
    require 'logger'
    $log = Logger.new(STDOUT)
    $log.level = Logger::DEBUG
  end
  $log.debug(msg)
  #puts msg
end


module FormModel
  
  def self.indent(line)
    if line =~ /^( +)[#{INDENT_PREX}]\s/ or line =~ /^( +)#{'-'*10}[^-]/
      return $1.size
    end
    -1
  end
  
  class FormSource
    def initialize(source)
        @file = source
    end
    
    #lazy
    def lines
      unless @lines
        @lines = []
        open(@file) do |f|
          while l = f.gets
            @lines << l
          end
        end
      end
      @lines
    end
  end

  class Scope
    attr_accessor :source, :begin_line, :end_line
    
    def initialize(source, *args)
      @source = source
      if args
        sets = %w[begin_line end_line]
        args.each_with_index do |v, idx|
          eval "@#{sets[idx]} = #{v}"
        end
      end
    end
    
    def begin_line
      @begin_line = 0 unless @begin_line
      @begin_line
    end

    def end_line
      @end_line = @source.lines.size unless @end_line
      @end_line
    end
    
    def subscope(sbl, sel=end_line)
      Scope.new(source, sbl, sel)
    end

    def substract(scope)
      return Scope.new(source, scope.end_line+1, end_line) if begin_line = scope.begin_line
      return Scope.new(source, begin_line, scope.begin_line-1) if end_line = scope.end_line
      raise "can't substract!"
    end

    def first 
      at(0)
    end
  
    def rest
      content[1 .. content.size]
    end

    def content
      @source.lines[begin_line .. end_line]
    end

    def at(pos)
      @source.lines[begin_line + pos]
    end

    def size
      end_line - begin_line
    end

    def empty?
      return end_line < begin_line
    end
    
    def to_s
      "begin_line:#{begin_line}, end_line:#{end_line}, first:#{first}"
    end
  end
  
  
  module MatchingPattern

    # match the scope that it's first line matches the given pattern
    def block_line(key)
      pattern = /^ +[#{INDENT_PREX}]\s+#{key}\s+$/
    
      lambda { |scope, element|
        not (scope.first =~ pattern).nil? 
      }
    end

    def name_line(key)
      pattern = /^ +[#{INDENT_PREX}]\s+#{key}/
    
      lambda { |scope, element|
        not (scope.first =~ pattern).nil? and element.level+2==FormModel.indent(scope.first)
      }
    end
    
    # match the text that have only one line or not nested structure
    def single_line
      lambda {|scope, element| 
        return true if scope.size == 1
        this_indent = FormModel.indent(scope.at(0))
        return false if this_indent == -1
        next_indent = FormModel.indent(scope.at(1))
        return this_indent==next_indent || next_indent==-1
      }
    end
  end

  module Install
    # setup parent's instance attribute
    # initialize the attribute to empty array
    # set the value of attribute is add value to the attribute
    def parent_array_install(sym)
      unless parent.respond_to? sym 
        parent.instance_eval <<-EOS, __FILE__, __LINE__
          unless defined? @#{sym}
            @#{sym} = []
          end

          def #{sym} 
            @#{sym}
          end
        
          def #{sym}=(v)
            @#{sym} << v
          end
        EOS
      end
    end

    def parent_single_install(sym, bool=false)
      whereto_single_install(parent, sym, bool)
    end

    def parent_parent_single_install(sym, bool=false)
      whereto_single_install(parent.parent, sym, bool)
    end

private
    def whereto_single_install(whereto, sym, bool=false)
      unless whereto.respond_to? sym 
        whereto.instance_eval <<-EOS, __FILE__, __LINE__
          unless defined? @#{sym}
            @#{sym} = nil
          end

          def #{sym}
            @#{sym}
          end
        
          def #{sym}=(v)
            @#{sym} = v
          end
        EOS
        if bool
          whereto.instance_eval <<-EOS, __FILE__, __LINE__
            def #{sym}?
              @#{sym} == BOOLEAN_TRUE
            end
          EOS
        end
      end
    end

  end

  module ScopeHelper
    def single_element
      self.class_eval <<-EOS, __FILE__, __LINE__
        def set_scope(scp)
          first_indent = FormModel.indent(scp.first)
          scp.rest.each_with_index do |l, idx|
            if FormModel.indent(l) == first_indent 
              to = scp.begin_line + idx
              @scope = scp.subscope(scp.begin_line, to)
              return
            end
          end
          @scope = scp
        end
      EOS
    end

    def array_element
      self.class_eval <<-EOS, __FILE__, __LINE__
        def set_scope(scp)
          first_indent = FormModel.indent(scp.first)
          scp.rest.each_with_index do |l, idx|
            if l =~ /#{'-'*10}[^-]/ and FormModel.indent(l) == first_indent 
              to = scp.begin_line + idx
              @scope = scp.subscope(scp.begin_line, to)
              return
            end
          end
          @scope = scp
        end
      EOS
    end
  end

  class FormElement 
    attr_accessor :scope, :parent, :children, :level
    
    extend MatchingPattern, ScopeHelper
    include Install, Keyword

    single_element
    
    def initialize(parent, scope)
      debug("#{self} init...")
      debug("scope:#{scope.to_s}")
      @parent = parent
      set_scope(scope)
      install
    end
    
    def install; end
       
    def level
      @level || @level=FormModel.indent(scope.first)
    end
    
    def add_child(child)
      @children = [] unless @children
      @children << child
    end
    
    def children(clazz=nil)
      do_parse
      return @children unless clazz
      return @children.select{ |c| c.is_a? clazz }
    end
  
    def self.register(pattern, clazz)
      @registered_pattern = [] unless @registered_pattern
      @registered_class_hash = {} unless @registered_class_hash
      @registered_pattern << pattern
      @registered_class_hash[pattern] = clazz
    end
    
    def self.match(scope, element)
      debug("try find the match for the following")
      debug("scope:#{scope.to_s}")
      if @registered_pattern
        @registered_pattern.each do |p|
          if p.call(scope, element)
            clazz = @registered_class_hash[p]
            debug("the match #{clazz} has been found")
            return clazz
          end
        end
      end
      debug("not match found")
      #if it doesn't match anything, then use FormElement as default 
      return FormElement
    end
    
    def parse(scope)
      clazz = self.class.match(scope, self)
      c = clazz.new(self, scope)
      add_child(c)
      scope.substract(c.scope)
    end
    
    def do_parse(s=scope)
      unless @parsed
        @parsed = true
        r = s
        while not r.empty?
          r = parse(r)
          yield r if block_given?
        end
      end
    end

    def respond_to?(arg)
      do_parse unless @parsed
      super
    end
    
    # we do lazy load!!
    # the advantage of the lazy is 
    # when we olny need the block of form, 
    # it's not necessary to parse all of form.
    def method_missing(name, *args, &block)
      debug("Method #{name} not found in #{self}(#{@parsed.nil? ? 'U':'L'})")
      if @parsed.nil?
        debug("Parsing #{self}")
        do_parse
        self.send(name, *args, &block)
      elsif self.respond_to?(name)
        super(name, *args, &block)
      else
        throw NoMethodError.new("undefined method '#{name}' for #{self}")
      end
    end

    
    def content
      self.scope.content
    end
  end
  
  class Property < FormElement

    attr_accessor :name, :value

    def install
      parent_array_install :properties
      parent.properties = self
      _init
      if has_name(name)
        n,b = for_name(name)
        parent_single_install(n, b)
        eval("parent.#{n} = value")
      end
    end
    
    private
    def _init
      scope.first =~ / +[#{INDENT_PREX}]\s([^\s]+)\s+(.*)/
      @name = $1
      @value = $2
      @value += "\n" + scope.rest.join if scope.size > 1
    end
  end

################################################################################
#TODO add dynamic class install support?
  def self.install_class(c) 
    self.module_eval <<-EOS, __FILE__, __LINE__
      class #{c} < FormElement
        register single_line, Property
        
        array_element
        
        def install
          parent.parent.#{c.downcase}s = self
          n = _name
          unless n.nil?
            parent_parent_single_install n.to_sym
            eval("parent.parent."+n+"= self")
          end
        end

        def _name
          begin
            unless @name
              @name = scope.first.split[2]
              @name = @name.downcase.gsub('-', '_')
            end
          ensure
            @name
          end
        end
      end 
 
      class #{c}s < FormElement
        register name_line(NAME_KEY), #{c}

        def install
          parent_array_install :#{c.downcase}s
          do_parse
        end

        def do_parse()
          super(scope.subscope(scope.begin_line+1))
        end
      end
    EOS
  end

  ALL = %w[Trigger Item Block Canvas Rgroup Vlist Win Punit Param ColMapProp]
  ALL.each { |c| install_class(c) }
################################################################################
  class Item < FormElement
    register block_line(TRIGGER_KEY), Triggers
    #register shortcut of type
    TYPE_ALL.each_key do |k|
      self.module_eval <<-EOS, __FILE__, __LINE__
        def is_#{k}?
          item_type == TYPE_#{k.upcase}
        end
      EOS
    end
  end

  class Vlist < FormElement
    register block_line(COLMAPPROP_KEY), ColMapProps
  end

  class Block < FormElement
    register block_line(ITEM_KEY), Items
    register block_line(TRIGGER_KEY), Triggers
  end

  class Form < FormElement
    register block_line(TRIGGER_KEY), Triggers
    register block_line(BLOCK_KEY), Blocks
    register block_line(VLIST_KEY), Vlists
    register block_line(PARAM_KEY), Params
    register block_line(PUNIT_KEY), Punits
    register block_line(RGROUP_KEY), Rgroups
    register block_line(WIN_KEY), Wins
    register block_line(CANVAS_KEY), Canvass
    register single_line, Property
    def initialize(file)
      @parent = nil
      @scope = Scope.new(FormSource.new(file))
    end
  end
end
